﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using Internal.KuiHuaBaoDian.Services.UGUI;
using Internal.KuiHuaBaoDian.Services.UI;
using KuiHuaBaoDian.Services.Asset;
using KuiHuaBaoDian.Services.Scene;
using KuiHuaBaoDian.Services.UGUI;
using KuiHuaBaoDian.Services.UGUI.Blocking;
using UnityEngine;
using UnityEngine.Assertions;

namespace KuiHuaBaoDian.Services.UGUI {

    /// <summary>
    /// UI服务
    /// </summary>
    public static class UIService {

        /// <summary>
        /// 单例
        /// </summary>
        public static IUIService Instance => m_Instance ??= KHBD.TypeMap.Instantiate<IUIService>();
        private static IUIService m_Instance;
    }

    /// <summary>
    /// UI服务
    /// </summary>
    public interface IUIService {

        bool LogEnabled { get; set; }

        UniTask<THandle> Goto<TUI, THandle>(IUIOptions options = null, IAssetDownloadProgress downloadProgress = null) where TUI : IUI where THandle : UIHandle;

        UniTask<UIHandle> Goto<TUI>(IUIOptions options = null, IAssetDownloadProgress downloadProgress = null) where TUI : IUI;

        UniTask<THandle> Goto<THandle>(Type uiType, IUIOptions options = null, IAssetDownloadProgress downloadProgress = null) where THandle : UIHandle;

        UniTask<UIHandle> Goto(Type uiType, IUIOptions options = null, IAssetDownloadProgress downloadProgress = null);

        UniTask Back(int skip = 0);

        void ClearAll();

        UniTask<THandle> Open<TUI, THandle>(IUIOptions options = null, IUIOpenOptions openOptions = null, IAssetDownloadProgress downloadProgress = null) where TUI : IUI where THandle : UIHandle;

        UniTask<UIHandle> Open<TUI>(IUIOptions options = null, IUIOpenOptions openOptions = null, IAssetDownloadProgress downloadProgress = null) where TUI : IUI;

        UniTask<THandle> Open<THandle>(Type uiType, IUIOptions options = null, IUIOpenOptions openOptions = null, IAssetDownloadProgress downloadProgress = null) where THandle : UIHandle;

        UniTask<UIHandle> Open(Type uiType, IUIOptions options = null, IUIOpenOptions openOptions = null, IAssetDownloadProgress downloadProgress = null);

        void Close(UIHandle handle);

        void ClearOpened();

        void SetActive(UIHandle handle, bool isActive, bool riseTop = false);

        bool IsActiveSelf(UIHandle handle);

        bool IsActiveSelf<TUI>() where TUI : IUI;

        /// <summary>
        /// 设置窗口的层然后重新排列
        /// </summary>
        /// <param name="ui"></param>
        /// <param name="layer"></param>
        void SetSortingLayer(UIHandle handle, string layerName);

        /// <summary>
        /// 派发事件
        /// </summary>
        /// <param name="event">事件</param>
        /// <returns>异步任务</returns>
        UniTask DispatchEvent(IUIEvent @event);

        /// <summary>
        /// 清理未被管理的顶层Canvas
        /// </summary>
        void ClearUnmanaged();

        [Obsolete("这是一个低效函数，不推荐使用")]
        bool IsOpened<TUI>();

        bool IsIdle { get; }
    }

    [Implement(typeof(IUIService))]
    internal sealed class KHBD_UIService : IUIService, IUIServiceInternal, IService {

        private record UIOpenedRecord(IUIOpenOptions Options, IUI UI, Type UIType);

        private readonly Stack<IUI> m_GotoStack = new(); //记录Goto顺序

        private readonly Dictionary<string, GameObject> m_CacheDictionary = new();
        private readonly Dictionary<Type, UIHandle> m_UniqueOpenedDictionary = new();
        private readonly Dictionary<UIHandle, UIOpenedRecord> m_OpenedDictionary = new(); //保存Open创建过的实体
        private readonly List<UIHandle> m_OpenedHandleList = new(); //保存Open创建过的句柄顺序

        private readonly IUIEventDispatcherInternal m_EventDispatcher = new KHBD_UIEventDispatcher();

        private bool m_IsRequiredLoading = false;
        private CancellationTokenSource m_CancellationTokenSource;
        private IUIInternal m_BlockingUI;
        private IUISettings m_Settings;

        private GameObject m_InitialContainer;

        public bool LogEnabled { get; set; } = true;

        private readonly HashSet<Type> m_LoadingUITypeSet = new();
        public bool IsIdle => m_LoadingUITypeSet.Count == 0;

        void IService.OnApplicationQuit() {
            m_GotoStack.Clear();
            m_UniqueOpenedDictionary.Clear();
            m_OpenedHandleList.Clear();
            m_OpenedDictionary.Clear();
            m_LoadingUITypeSet.Clear();
            m_CacheDictionary.Clear();
        }

        async UniTask IUIServiceInternal.RegisterBlocking<TUI>(IAssetDownloadProgress downloadProgress) {
            var ui = await Instantiate<TUI>(downloadProgress: downloadProgress);
            if (KHBD.Context.IsApplicationQuit) {
                return;
            }
            if (ui is IUIInternal @internal) {
                m_BlockingUI = @internal;
                m_BlockingUI.SetActive(false);
            } else {
                throw new InvalidOperationException("注册BlockingUI失败");
            }
        }

        void IUIServiceInternal.SetSettings(IUISettings value) {
            Assert.IsNotNull(value?.UICamera);
            Assert.IsNotNull(value?.CanvasPrefab);

            m_Settings = value;
        }

        public async UniTask<THandle> Goto<TUI, THandle>(IUIOptions options = null, IAssetDownloadProgress downloadProgress = null) where TUI : IUI where THandle : UIHandle => ValidateHandle<THandle>(await Goto<TUI>(options, downloadProgress), typeof(TUI));

        public async UniTask<UIHandle> Goto<TUI>(IUIOptions options = null, IAssetDownloadProgress downloadProgress = null) where TUI : IUI => await Goto(typeof(TUI), options, downloadProgress);

        public async UniTask<THandle> Goto<THandle>(Type uiType, IUIOptions options = null, IAssetDownloadProgress downloadProgress = null) where THandle : UIHandle => ValidateHandle<THandle>(await Goto(uiType, options, downloadProgress), uiType);

        public async UniTask<UIHandle> Goto(Type uiType, IUIOptions options = null, IAssetDownloadProgress downloadProgress = null) {
            Assert.IsNotNull(uiType);

            m_LoadingUITypeSet.Add(uiType);
            ClearOpened();
            if (m_GotoStack.Count > 0) {
                var lastUI = m_GotoStack.Peek();
                (lastUI as IUIInternal).SetActive(false);
                await (lastUI as IUIInternal).OnGotoOut();
            }
            await SceneService.Instance.WaitForReleaseDone();
            var ui = await Instantiate(uiType, options, downloadProgress);
            if (ui != null) {
                m_GotoStack.Push(ui);
                if (ui is IUIInternal @internal) {
                    @internal.SetActive(true);
                } else {
                    m_LoadingUITypeSet.Remove(uiType);
                    throw new NotImplementedException($"{uiType.FullName} 没有实现 {typeof(IUIInternal).FullName}");
                }
                m_LoadingUITypeSet.Remove(uiType);
                return ui.Handle;
            } else {
                m_LoadingUITypeSet.Remove(uiType);
                return null;
            }
        }

        public async UniTask Back(int skip = 0) {
            skip = skip < 0 ? 0 : skip;
            if (m_GotoStack.Count > 0) {
                ClearOpened();
                IUI currentUI;
                for (int i = 0; i <= skip; ++i) {
                    currentUI = m_GotoStack.Pop();
                    (currentUI as IUIInternal).SetActive(false);
                    (currentUI as IDisposable)?.Dispose();
                }
                if (m_GotoStack.Count > 0) {
                    currentUI = m_GotoStack.Peek();
                    (currentUI as IUIInternal).SetActive(true);
                    await (currentUI as IUIInternal).OnBackIn();
                }
            }
        }

        public void SetActive(UIHandle handle, bool isActive, bool riseTop = false) {
            Assert.IsNotNull(handle);

            if (m_OpenedDictionary.TryGetValue(handle, out var record)) {
                (record.UI as IUIInternal).SetActive(isActive);
                if (riseTop) {
                    m_OpenedHandleList.Remove(handle);
                    m_OpenedHandleList.Add(handle);
                }
                SortOpened();
            }
        }

        public bool IsActiveSelf(UIHandle handle) {
            Assert.IsNotNull(handle);

            bool activeSelf = false;
            if (m_OpenedDictionary.TryGetValue(handle, out var record)) {
                activeSelf = record.UI.IsActiveSelf;
            }
            return activeSelf;
        }

        public bool IsActiveSelf<TUI>() where TUI : IUI {
            var ui = GetOpened<TUI>();
            if (ui != null) {
                return ui.IsActiveSelf;
            }
            return false;
        }

        public void ClearAll() {
            while (m_GotoStack.Count > 0 && KHBD.Context.IsApplicationPlaying) {
                var ui = m_GotoStack.Pop();
                (ui as IDisposable)?.Dispose();
            }
            ClearOpened();
        }

        public void ClearOpened() {
            m_BlockingUI?.SetActive(false);
            (m_BlockingUI as IBlockingUI)?.SetTargetUIHandle(null);
            var index = 0;
            while (m_OpenedHandleList.Count > index && KHBD.Context.IsApplicationPlaying) {
                var handle = m_OpenedHandleList[index];
                var record = m_OpenedDictionary[handle];
                var isAutoCloseOnClear = record.Options == null || record.Options.IsAutoCloseOnClear;
                if (isAutoCloseOnClear) {
                    m_OpenedHandleList.RemoveAt(index);
                    (record.UI as IDisposable)?.Dispose();
                    m_OpenedDictionary.Remove(handle);
                    m_UniqueOpenedDictionary.Remove(record.UIType);
                } else {
                    index++;
                }
            }
        }

        public void ClearUnmanaged() {
            if (KHBD.Context.IsApplicationQuit) {
                return;
            }
            var canvases = UnityEngine.Object.FindObjectsByType<Canvas>(FindObjectsInactive.Include, FindObjectsSortMode.None);
            foreach (var canvas in canvases) {
                if (canvas.transform.parent != null) {
                    continue;
                }
                if (canvas.gameObject.TryGetComponent<IUIView>(out _)) {
                    continue;
                }
                UnityEngine.Object.Destroy(canvas.gameObject);
            }
        }

        private async UniTask<TUI> Instantiate<TUI>(IUIOptions options = null, IAssetDownloadProgress downloadProgress = null) where TUI : IUI => (TUI)await Instantiate(typeof(TUI), options, downloadProgress);

        private async UniTask<IUI> Instantiate(Type uiType, IUIOptions options = null, IAssetDownloadProgress downloadProgress = null) {
            Assert.IsTrue(m_Settings?.UICamera != null, $"没有配置UI相机, 请使用{nameof(IUIServiceInternal)}.{nameof(IUIServiceInternal.SetSettings)}({nameof(IUISettings)})进行配置");
            if (m_InitialContainer == null) {
                m_InitialContainer = new GameObject(nameof(UIService)) {
                    hideFlags = HideFlags.HideAndDontSave
                };
                m_InitialContainer.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
                m_InitialContainer.transform.localScale = Vector3.one;
                m_InitialContainer.SetActive(false);
            }

            var uiImplementedType = KHBD.TypeMap.Fetch(uiType);
            if (uiImplementedType != null) {
                Assert.IsTrue(typeof(IUIInternal).IsAssignableFrom(uiImplementedType));
                Assert.IsTrue(typeof(IDisposable).IsAssignableFrom(uiImplementedType));

                await KHBD.Context.WaitUntil(() => !m_IsRequiredLoading, CancellationToken.None);

                if (m_CancellationTokenSource != null) {
                    m_CancellationTokenSource.Cancel();
                    m_CancellationTokenSource.Dispose();
                    m_CancellationTokenSource = null;
                }
                var interfaceType = uiType;
                var ui = (IUI)Activator.CreateInstance(uiImplementedType);
                m_CancellationTokenSource = new();
                if (LogEnabled) {
                    UnityEngine.Debug.Log($"<color=#00DEFF>开始加载</color> {nameof(UIService)}.{nameof(Instantiate)}<{interfaceType.FullName}>()");
                    m_CancellationTokenSource.Token.Register(() => {
                        UnityEngine.Debug.Log($"<color=#FFDD4A>放弃加载</color> {nameof(UIService)}.{nameof(Instantiate)}<{interfaceType.FullName}>()");
                    });
                }
                m_IsRequiredLoading = options != null && options.IsRequired;
                var theme = options != null ? options.Theme : UIOptions.THEME_DEFAULT;
                var request = AssetRequest.Instantiate($"{ui.AssetKey}_{theme}", m_CancellationTokenSource.Token, downloadProgress, false);
                GameObject instance = null;
                try {
                    await ui.Preload(downloadProgress);
                    instance = await AssetService.Instance.Instantiate(request, m_InitialContainer.transform, false);
                    if (instance != null) {
                        instance.transform.SetParent(null);
                        if (!instance.TryGetComponent<CanvasGroup>(out var canvasGroup)) {
                            canvasGroup = instance.AddComponent<CanvasGroup>();
                        }
                        canvasGroup.interactable = true;
                        canvasGroup.blocksRaycasts = true;
                        var canvas = instance.GetComponent<Canvas>();
                        canvas.gameObject.SetActive(false);
                        canvas.renderMode = RenderMode.ScreenSpaceCamera;
                        var cameraIndex = ui.CameraIndex;
                        if (cameraIndex > -1 && m_Settings.OtherCameras.Count <= cameraIndex) {
                            UnityEngine.Debug.LogWarning($"指定了其它相机，但相机没有在配置中找到 (ui={uiImplementedType.FullName}, cameraIndex={cameraIndex}, settings other camera count={m_Settings.OtherCameras.Count})");
                            cameraIndex = -1;
                        }
                        canvas.worldCamera = cameraIndex <= -1 ? m_Settings.UICamera : m_Settings.OtherCameras[cameraIndex];
                        canvas.sortingLayerName = ui.Layer;
                        (ui as IUIInternal).Initialize(canvas, m_EventDispatcher);
                    }
                } catch (Exception e) {
                    m_LoadingUITypeSet.Remove(uiType);
                    throw new Exception($"加载UI时发生未知错误 (ui={uiImplementedType.FullName})", e);
                }
                if (m_CancellationTokenSource != null) {
                    m_CancellationTokenSource.Dispose();
                    m_CancellationTokenSource = null;
                }
                m_IsRequiredLoading = false;
                if (instance != null) {
                    if (LogEnabled) {
                        UnityEngine.Debug.Log($"<color=#4CFF98>加载完成</color> {nameof(UIService)}.{nameof(Instantiate)}<{interfaceType.FullName}>()");
                    }
                    await (ui as IUIInternal).Instantiated();
                    return ui;
                } else {
                    (ui as IDisposable).Dispose();
                    if (LogEnabled && !KHBD.Context.IsApplicationQuit) {
                        UnityEngine.Debug.Log($"<color=#FF0032>加载失败</color> {nameof(UIService)}.{nameof(Instantiate)}<{interfaceType.FullName}>()");
                    }
                    return null;
                }
            } else {
                return null;
            }
        }

        public THandle ValidateHandle<THandle>(UIHandle handle, Type uiType) where THandle : UIHandle {
            if (handle == null) {
                return null;
            }
            var tHandle = handle as THandle;
            Assert.IsNotNull(tHandle, $"{uiType.Name}类的泛型参数列表中没有找到{typeof(THandle).Name}");
            return tHandle;
        }

        public async UniTask<THandle> Open<TUI, THandle>(IUIOptions options = null, IUIOpenOptions openOptions = null, IAssetDownloadProgress downloadProgress = null) where TUI : IUI where THandle : UIHandle => ValidateHandle<THandle>(await Open<TUI>(options, openOptions, downloadProgress), typeof(TUI));

        public async UniTask<UIHandle> Open<TUI>(IUIOptions options = null, IUIOpenOptions openOptions = null, IAssetDownloadProgress downloadProgress = null) where TUI : IUI => await Open(typeof(TUI), options, openOptions, downloadProgress);

        public async UniTask<THandle> Open<THandle>(Type uiType, IUIOptions options = null, IUIOpenOptions openOptions = null, IAssetDownloadProgress downloadProgress = null) where THandle : UIHandle => ValidateHandle<THandle>(await Open(uiType, options, openOptions, downloadProgress), uiType);

        public async UniTask<UIHandle> Open(Type uiType, IUIOptions options = null, IUIOpenOptions openOptions = null, IAssetDownloadProgress downloadProgress = null) {
            Assert.IsNotNull(uiType);

            if (openOptions?.Blocking != null) {
                m_BlockingUI?.SetActive(true);
                (m_BlockingUI as IBlockingUI)?.SetOptions(openOptions.Blocking);
            }
            if (openOptions?.IsUnique == true) {
                if (m_UniqueOpenedDictionary.TryGetValue(uiType, out var handle)) {
                    if (handle == null) {
                        await KHBD.Context.WaitUntil(() => {
                            if (m_UniqueOpenedDictionary.TryGetValue(uiType, out handle)) {
                                return handle != null;
                            }
                            return true;
                        }, CancellationToken.None);
                    }
                    return handle;
                } else {
                    m_UniqueOpenedDictionary[uiType] = null;
                }
            }
            m_LoadingUITypeSet.Add(uiType);
            var ui = await Instantiate(uiType, options, downloadProgress);
            if (ui != null) {
                var handle = ui.Handle;
                if (openOptions?.Blocking != null) {
                    (m_BlockingUI as IBlockingUI)?.SetTargetUIHandle(handle);
                }
                m_OpenedDictionary[handle] = new UIOpenedRecord(openOptions, ui, uiType);
                m_OpenedHandleList.Add(handle);
                var isActive = openOptions?.IsActive ?? true;
                if (isActive) {
                    (ui as IUIInternal).SetActive(true);
                }
                SortOpened();
                m_UniqueOpenedDictionary[uiType] = handle;
                m_LoadingUITypeSet.Remove(uiType);
                return handle;
            } else {
                m_LoadingUITypeSet.Remove(uiType);
                return null;
            }
        }

        public void Close(UIHandle handle) {
            var index = m_OpenedHandleList.IndexOf(handle);
            if (index > -1) {
                var record = m_OpenedDictionary[handle];
                m_OpenedHandleList.RemoveAt(index);
                m_OpenedDictionary.Remove(handle);
                m_UniqueOpenedDictionary.Remove(record.UIType);
                (record.UI as IUIInternal).SetActive(false);
                (record.UI as IDisposable).Dispose();
                SortOpened();
            }
        }

        public void SetSortingLayer(UIHandle handle, string layerName) {
            var ui = GetOpened(handle);
            (ui as IUIInternal).SetSortingLayerName(layerName);
            SortOpened();
        }

        private void SortOpened() {
            var sortingOrder = 0;
            var maxSortingLayerValue = int.MinValue;
            var hasBlocking = false;
            for (int i = m_OpenedHandleList.Count - 1; i > -1; i--) {
                var handle = m_OpenedHandleList[i];
                var record = m_OpenedDictionary[handle];
                if (record.UI is IUIInternal @internal) {
                    sortingOrder = @internal.SetSortingOrder(sortingOrder);
                    sortingOrder--;
                    if (record.Options?.Blocking != null && record.UI.IsActiveSelf) {
                        var sortingLayerValue = SortingLayer.GetLayerValueFromID(record.UI.SortingLayer);
                        if (!hasBlocking || sortingLayerValue > maxSortingLayerValue) {
                            hasBlocking = true;
                            maxSortingLayerValue = sortingLayerValue;
                            m_BlockingUI?.SetSortingLayer(record.UI.SortingLayer);
                            var exsitBlocking = m_BlockingUI?.SetSortingOrder(sortingOrder);
                            if (exsitBlocking.HasValue) {
                                (m_BlockingUI as IBlockingUI).SetOptions(record.Options.Blocking);
                                (m_BlockingUI as IBlockingUI).SetTargetUIHandle(record.UI.Handle);
                                sortingOrder = exsitBlocking.Value;
                                sortingOrder--;
                            }
                        }
                    }
                }
            }
            m_BlockingUI?.SetActive(hasBlocking);
            if (!hasBlocking) {
                (m_BlockingUI as IBlockingUI)?.SetTargetUIHandle(null);
            }
        }

        void IUIServiceInternal.SetAllBlocksRaycasts(bool isBlocksRaycasts) {
            foreach (var ui in m_GotoStack) {
                if (ui is IUIInternal uiInternal) {
                    uiInternal.SetBlocksRaycasts(isBlocksRaycasts);
                }
            }
            foreach (var kv in m_OpenedDictionary) {
                var record = kv.Value;
                if (record.UI is IUIInternal uiInternal) {
                    uiInternal.SetBlocksRaycasts(isBlocksRaycasts);
                }
            }
        }

        //public void SetBlocksRaycasts(IUIHandle handle, bool isBlocksRaycasts) {
        //    if (m_Dictionary.TryGetValue(handle, out var record)) {
        //        (record.UI as IUIInternal).SetBlocksRaycasts(isBlocksRaycasts);
        //    }
        //}

        Canvas IUIServiceInternal.InstantiateCanvas(string name) {
            var canvas = UnityEngine.Object.Instantiate(m_Settings.CanvasPrefab);
            canvas.gameObject.name = $"canvas_{name}";
            canvas.transform.ResetLocalMatrix();
            return canvas;
        }

        IUI IUIServiceInternal.GetUI(string name) {
            if (m_GotoStack.TryPeek(out var current) && current.Name == name) {
                return current;
            } else {
                return GetOpened(name);
            }
        }

        IUI IUIServiceInternal.GetOpened(UIHandle handle) => GetOpened(handle);
        private IUI GetOpened(UIHandle handle) {
            if (m_OpenedDictionary.TryGetValue(handle, out var result)) {
                return result.UI;
            }
            return null;
        }

        IUI IUIServiceInternal.GetOpened<TUI>() => GetOpened<TUI>();
        private IUI GetOpened<TUI>() {
            var type = typeof(TUI);
            var kv = m_OpenedDictionary.FirstOrDefault(kv => kv.Value.UIType == type);
            if (kv.Value != null) {
                return kv.Value.UI;
            }
            return null;
        }

        IUI IUIServiceInternal.GetOpened(string name) => GetOpened(name);
        private IUI GetOpened(string name) {
            var kv = m_OpenedDictionary.FirstOrDefault(kv => kv.Value.UI.Name == name);
            if (kv.Value != null) {
                return kv.Value.UI;
            }
            return null;
        }

        public bool IsOpened<TUI>() {
            var type = typeof(TUI);
            var kv = m_OpenedDictionary.FirstOrDefault(kv => kv.Value.UIType == type);
            return kv.Value != null && kv.Value.UI.IsActiveSelf;
        }

        public UniTask DispatchEvent(IUIEvent @event) {
            return m_EventDispatcher.Dispatch(@event);
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.UI {

    public interface IUIServiceInternal {

        void SetSettings(IUISettings value);

        UniTask RegisterBlocking<TUI>(IAssetDownloadProgress downloadProgress) where TUI : class, IBlockingUI, IUI;

        void SetAllBlocksRaycasts(bool isBlocksRaycasts);

        Canvas InstantiateCanvas(string name);

        IUI GetOpened(UIHandle handle);

        [Obsolete("这是一个低效函数，不推荐使用")]
        IUI GetOpened<TUI>() where TUI : IUI;

        [Obsolete("这是一个低效函数，不推荐使用")]
        IUI GetOpened(string name);

        [Obsolete("这是一个低效函数，不推荐使用")]
        IUI GetUI(string name);
    }
}